1
|
|
|
/** |
2
|
|
|
* @module http/_common |
3
|
|
|
*/ |
4
|
|
|
|
5
|
|
|
var Schema = require('./_schema') |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Simple inheritance establisher |
9
|
|
|
* |
10
|
|
|
* @param parent |
11
|
|
|
* @param child |
12
|
|
|
* @param name |
13
|
|
|
*/ |
14
|
|
|
function extend (parent, child, name) { |
15
|
|
|
child.prototype = Object.create(parent.prototype) |
16
|
|
|
child.prototype.constructor = child |
17
|
|
|
child.prototype.name = name |
18
|
|
|
} |
19
|
|
|
|
20
|
|
|
function NetworkException (message, code, request) { |
21
|
|
|
this.message = message || this.message |
22
|
|
|
this.code = code || this.code |
23
|
|
|
this.request = request |
24
|
|
|
this.stack = new Error().stack |
25
|
|
|
} |
26
|
|
|
extend(Error, NetworkException, 'NetworkException') |
27
|
|
|
var N = NetworkException |
28
|
|
|
N.prototype.code = -1 |
29
|
|
|
N.prototype.message = 'Unexpected exception during request' |
30
|
|
|
|
31
|
|
|
var index = { |
32
|
|
|
'-1': N |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @class {IllegalUrlException} |
37
|
|
|
* |
38
|
|
|
* @property {string} message |
39
|
|
|
* @property {int} code |
40
|
|
|
*/ |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @class {MissingHostException} |
44
|
|
|
* |
45
|
|
|
* @property {string} message |
46
|
|
|
* @property {int} code |
47
|
|
|
*/ |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @class {ConnectionErrorException} |
51
|
|
|
* |
52
|
|
|
* @property {string} message |
53
|
|
|
* @property {int} code |
54
|
|
|
*/ |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @class {RedirectVortexException} |
58
|
|
|
* |
59
|
|
|
* @property {string} message |
60
|
|
|
* @property {int} code |
61
|
|
|
*/ |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @class {NetworkErrorException} |
65
|
|
|
* |
66
|
|
|
* @property {string} message |
67
|
|
|
* @property {int} code |
68
|
|
|
*/ |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @class {TimeoutException} |
72
|
|
|
* |
73
|
|
|
* @property {string} message |
74
|
|
|
* @property {int} code |
75
|
|
|
*/ |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @class {VoxEngineErrorException} |
79
|
|
|
* |
80
|
|
|
* @property {string} message |
81
|
|
|
* @property {int} code |
82
|
|
|
*/ |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @namespace |
86
|
|
|
* |
87
|
|
|
* @property {Function} IllegalUrlException |
88
|
|
|
* @property {Function} MissingHostException |
89
|
|
|
* @property {Function} ConnectionErrorException |
90
|
|
|
* @property {Function} RedirectVortexException |
91
|
|
|
* @property {Function} NetworkErrorException |
92
|
|
|
* @property {Function} TimeoutException |
93
|
|
|
* @property {Function} VoxEngineErrorException |
94
|
|
|
*/ |
95
|
|
|
var exports = { |
96
|
|
|
NetworkException: N, |
97
|
|
|
codeExceptionIndex: index, |
98
|
|
|
Method: Schema.Method |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Declares new NetworkException (resembling VoxImplant HTTP client |
103
|
|
|
* negative response codes) |
104
|
|
|
* |
105
|
|
|
* @param {string} name Exception name |
106
|
|
|
* @param {int} code Exception code (response code) |
107
|
|
|
* @param {string} message Default exception message |
108
|
|
|
*/ |
109
|
|
|
function declare (name, code, message) { |
110
|
|
|
var e = function () { |
111
|
|
|
N.apply(this, arguments) |
112
|
|
|
} |
113
|
|
|
extend(N, e, name) |
114
|
|
|
e.prototype.constructor = e |
115
|
|
|
e.prototype.code = e.code = code |
116
|
|
|
e.prototype.message = message |
117
|
|
|
exports[name] = e |
118
|
|
|
index[code] = e |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
declare('IllegalUrlException', -2, 'Illegal URL exception') |
122
|
|
|
declare('MissingHostException', -3, 'Could not find host') |
123
|
|
|
declare('ConnectionErrorException', -4, 'Could not establish connection') |
124
|
|
|
declare('RedirectVortexException', -5, 'Too many redirects') |
125
|
|
|
declare('NetworkErrorException', -6, 'Network error exception') |
126
|
|
|
declare('TimeoutException', -7, 'Request timeout exceeded') |
127
|
|
|
declare('VoxEngineErrorException', -8, 'Internal exception during request') |
128
|
|
|
|
129
|
|
|
function HttpException (message, request, response) { |
130
|
|
|
this.message = message |
131
|
|
|
this.request = request |
132
|
|
|
this.response = response |
133
|
|
|
this.stack = new Error().stack |
134
|
|
|
} |
135
|
|
|
extend(Error, HttpException, 'HttpException') |
136
|
|
|
exports.HttpException = HttpException |
137
|
|
|
|
138
|
|
|
function ServerErrorException () { |
139
|
|
|
HttpException.apply(this, arguments) |
140
|
|
|
} |
141
|
|
|
extend(HttpException, ServerErrorException, 'ServerErrorException') |
142
|
|
|
exports.ServerErrorException = ServerErrorException |
143
|
|
|
|
144
|
|
|
function ClientErrorException () { |
145
|
|
|
HttpException.apply(this, arguments) |
146
|
|
|
} |
147
|
|
|
extend(HttpException, ClientErrorException, 'ClientErrorException') |
148
|
|
|
exports.ClientErrorException = ClientErrorException |
149
|
|
|
|
150
|
|
|
function NotFoundException () { |
151
|
|
|
HttpException.apply(this, arguments) |
152
|
|
|
} |
153
|
|
|
extend(HttpException, NotFoundException, 'NotFoundException') |
154
|
|
|
exports.NotFoundException = NotFoundException |
155
|
|
|
|
156
|
|
|
function InvalidConfigurationException (message) { |
157
|
|
|
this.message = message || 'Invalid configuration' |
158
|
|
|
this.stack = new Error().stack |
159
|
|
|
} |
160
|
|
|
extend(Error, InvalidConfigurationException, 'InvalidConfigurationException') |
161
|
|
|
exports.InvalidConfigurationException = InvalidConfigurationException |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Normalizes provided bag, ensuring that it's an object where every |
165
|
|
|
* key corresponds to value of array of strings |
166
|
|
|
* |
167
|
|
|
* @param bag |
168
|
|
|
* @return {Object} |
169
|
|
|
*/ |
170
|
|
|
function normalize (bag) { |
171
|
|
|
bag = bag || {} |
172
|
|
|
Object.keys(bag).forEach(function (key) { |
173
|
|
|
if (!(bag[key] instanceof Array)) { |
174
|
|
|
bag[key] = [bag[key]] |
175
|
|
|
} |
176
|
|
|
}) |
177
|
|
|
return bag |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** @deprecated */ |
181
|
|
|
exports.Params = { |
182
|
|
|
normalize: normalize |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
exports.Query = { |
186
|
|
|
/** |
187
|
|
|
* Encodes provided query to a url-safe string |
188
|
|
|
* |
189
|
|
|
* @param {Query} query |
190
|
|
|
* @returns {string} |
191
|
|
|
*/ |
192
|
|
|
encode: function (query) { |
193
|
|
|
query = normalize(query || {}) |
194
|
|
|
return Object.keys(query).reduce(function (carrier, key) { |
195
|
|
|
return carrier.concat(query[key].map(function (value) { |
196
|
|
|
return encodeURIComponent(key) + '=' + encodeURIComponent(value) |
197
|
|
|
})) |
198
|
|
|
}, []).join('&') |
199
|
|
|
}, |
200
|
|
|
normalize: normalize |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
exports.Headers = { |
204
|
|
|
/** |
205
|
|
|
* Encodes headers object to VoxEngine / protocol presentation |
206
|
|
|
* |
207
|
|
|
* @param {Headers} headers |
208
|
|
|
* @returns {string[]} |
209
|
|
|
*/ |
210
|
|
|
encode: function (headers) { |
211
|
|
|
headers = normalize(headers || {}) |
212
|
|
|
return Object.keys(headers).reduce(function (_, k) { |
213
|
|
|
return _.concat(headers[k].map(function (v) { |
214
|
|
|
return k + ': ' + v |
215
|
|
|
})) |
216
|
|
|
}, []) |
217
|
|
|
}, |
218
|
|
|
/** |
219
|
|
|
* Headers in VoxImplant come in key-value pairs: |
220
|
|
|
* |
221
|
|
|
* - key: Server |
222
|
|
|
* value: nginx/1.9.9 |
223
|
|
|
* - key: Link |
224
|
|
|
* value: Wed, 09 Aug 2017 18:16:58 GMT |
225
|
|
|
* - key: Link |
226
|
|
|
* value: <https://voximplant.com/>; rel="home" |
227
|
|
|
* - key: Link |
228
|
|
|
* value: <https://voximplant.com/docs>; rel="documentation" |
229
|
|
|
* |
230
|
|
|
* ([reference](http://voximplant.com/docs/references/appengine/Net.HttpRequestResult.html)) |
231
|
|
|
* |
232
|
|
|
* This method converts such pairs into an object with headers collected by |
233
|
|
|
* their key: |
234
|
|
|
* |
235
|
|
|
* Server: |
236
|
|
|
* - nginx/1.9.9 |
237
|
|
|
* Link: |
238
|
|
|
* - <https://voximplant.com/>; rel="home" |
239
|
|
|
* - <https://voximplant.com/docs>; rel="documentation" |
240
|
|
|
* |
241
|
|
|
* @param {Object[]} headers |
242
|
|
|
* @return {Headers} |
243
|
|
|
*/ |
244
|
|
|
decode: function (headers) { |
245
|
|
|
return (headers || []).reduce(function (carrier, item) { |
246
|
|
|
carrier[item.key] = carrier[item.key] || [] |
247
|
|
|
carrier[item.key].push(item.value) |
248
|
|
|
return carrier |
249
|
|
|
}, {}) |
250
|
|
|
}, |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Merges two or more headers definitions, using latter's values on |
254
|
|
|
* intersecting keys. |
255
|
|
|
* |
256
|
|
|
* @param {...Headers} args |
257
|
|
|
* @returns {Headers} |
258
|
|
|
*/ |
259
|
|
|
override: function (args) { |
260
|
|
|
return [] |
261
|
|
|
.filter.call(arguments, function (_) { return _ }) |
262
|
|
|
.reduce(function (carrier, _) { |
263
|
|
|
Object.keys(_).forEach(function (key) { carrier[key] = _[key] }) |
264
|
|
|
return carrier |
265
|
|
|
}, {}) |
266
|
|
|
}, |
267
|
|
|
normalize: normalize |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** @deprecated */ |
271
|
|
|
exports.Headers.merge = exports.Headers.override |
272
|
|
|
/** @deprecated */ |
273
|
|
|
exports.headers = exports.Headers |
274
|
|
|
/** @deprecated */ |
275
|
|
|
exports.query = exports.Query |
276
|
|
|
/** @deprecated */ |
277
|
|
|
exports.params = exports.Params |
278
|
|
|
module.exports = exports |
279
|
|
|
|